﻿namespace BM.Tools.WebControls.MForm.Design.PropertiesGeneration
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;    
    
    using BM.Tools.WebControls.MForm.Controls;
    using EnvDTE;
    using EnvDTE80;

    /// <summary>
    /// This class is used to generate MForm properties according to the provided property definitions.
    /// </summary>    
    internal class PropertiesGenerator
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="PropertiesGenerator"/> class.
        /// </summary>
        /// <param name="root">
        /// The root control.
        /// </param>
        /// <param name="projectItem">
        /// The project item.
        /// </param>
        /// <param name="propertyDefinitions">
        /// The property definitions.
        /// </param>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This code is invoked internally by the designer")]
        public PropertiesGenerator(Root root, ProjectItem projectItem, IEnumerable<PropertyDefinition> propertyDefinitions)
        {
            Root = root;
            ProjectItem = projectItem;
            PropertyDefinitions = propertyDefinitions;
        }

        /// <summary>
        /// Gets the Root control.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This code is invoked internally by the designer")]
        public Root Root
        {
            get; 
            private set;
        }

        /// <summary>
        /// Gets the ProjectItem.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This code is invoked internally by the designer")]
        public ProjectItem ProjectItem
        {
            get; 
            private set;
        }

        /// <summary>
        /// Gets the existing or newly created project item that will keep the MForm properties of the root control.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This code is invoked internally by the designer")]
        public ProjectItem MFormProjectItem
        {
            get
            {
                return SearchForMFormProjectItem() ?? CreateMFormProjectItem();
            }
        }

        /// <summary>
        /// Gets the propvided property definitions.
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This code is invoked internally by the designer")]
        public IEnumerable<PropertyDefinition> PropertyDefinitions
        {
            get; 
            private set;
        }

        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This code is invoked internally by the designer")]
        private string MFormProjectItemSuffix
        {
            get
            {
                return string.Concat(".", Root.ID, ".mform.cs");
            }
        }

        /// <summary>
        /// Generates the properties according to the definitions provided
        /// </summary>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This code is invoked internally by the designer")]
        public void Generate()
        {
            var model = (FileCodeModel2)MFormProjectItem.FileCodeModel;
            var classElement = SearchForClassElement(model.CodeElements);
            GeneratePropertiesInClass(classElement);    
        }

        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This code is invoked internally by the designer")]
        private static CodeClass2 SearchForClassElement(CodeElements codeElements)
        {
            foreach (CodeElement codeElement in codeElements)
            {
                if (codeElement.Kind == vsCMElement.vsCMElementClass)
                {
                    return codeElement as CodeClass2;
                }

                var childCodeElement = SearchForClassElement(codeElement.Children);
                if (childCodeElement != null)
                {
                    return childCodeElement;
                }
            }

            return null;
        }

        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This code is invoked internally by the designer")]
        private static void RemoveMembers(CodeClass2 codeClass)
        {
            var children = new List<CodeElement>();
            foreach (CodeElement child in codeClass.Children)
            {
                children.Add(child);
            }

            foreach (var child in children)
            {
                codeClass.RemoveMember(child);
            }
        }

        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This code is invoked internally by the designer")]
        private ProjectItem SearchForMFormProjectItem()
        {
            foreach (ProjectItem projectItem in ProjectItem.ProjectItems)
            {
                if (projectItem.Name.EndsWith(MFormProjectItemSuffix, StringComparison.Ordinal))
                {
                    return projectItem;
                }
            }

            return null;
        }

        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This code is invoked internally by the designer")]
        private ProjectItem CreateMFormProjectItem()
        {
            return ProjectItem.ProjectItems.AddFromTemplate(ProjectItem.Document.FullName + ".designer.cs", Path.GetFileNameWithoutExtension(ProjectItem.Name) + MFormProjectItemSuffix);
        }

        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This code is invoked internally by the designer")]
        private void GeneratePropertiesInClass(CodeClass2 codeClass)
        {
            RemoveMembers(codeClass);
            foreach (var propertyDefinition in PropertyDefinitions)
            {
                var propertyGenerator = new PropertyGenerator(Root, codeClass, propertyDefinition);
                propertyGenerator.Generate();
            }            
        }        
    }
}
